/*
 * @features: 主要功能
 * @description: 内容说明
 * @Date: 2021-09-28 10:25:19
 * @Author: judu233(769471424@qq.com)
 * @LastEditTime: 2022-08-31 15:16:16
 * @LastEditors: judu233
*/

import { ExtendsLoad } from "../CCExtends";

type Circles = {
    radius: number;
    x: number;
    y: number;
}

@ExtendsLoad(cc.math)
export class MathExtends {
    /**
     * 随时间变化进度值 
     * @param start 开始
     * @param end 结束
     * @param t 时间
     * @returns 
     */
    static progress(start: number, end: number, t: number) {
        return start + (end - start) * t;
    }

    /**
     * 角度插值
     * @param current 
     * @param target 
     * @param t 
     * @returns 
     */
    static lerpAngle(current: number, target: number, t: number) {
        current %= 360;
        target %= 360;

        let dAngle: number = target - current;
        if (dAngle > 180)
            target = current - (360 - dAngle);
        else if (dAngle < -180)
            target = current + (360 + dAngle);
        return (cc.misc.lerp(current, target, t) % 360 + 360) % 360;
    }

    /**
     * 取value的小数部分
     * @param value 
     * @returns 
     */
    static decimal(value: number) {
        return value - (Math.floor(value));
    }

    /**
     * 按一定的速度从一个角度转向令一个角度
     * @param current 
     * @param target 
     * @param speed 
     * @example
     */
    static angleTowards(current: number, target: number, speed: number): number {
        current %= 360;
        target %= 360;

        let dAngle: number = target - current;
        if (dAngle > 180)
            target = current - (360 - dAngle);
        else if (dAngle < -180)
            target = current + (360 + dAngle);

        let dir = target - current;
        if (speed > Math.abs(dir)) {
            return target;
        }
        return ((current + speed * Math.sign(dir)) % 360 + 360) % 360;
    }

    /**
     * 从 0 等分 space
     * 一般用于 for 找位置
     * @param {number} space 间距
     * @param {number} index 当前的索引
     * @param {number} length 最大的索引值
     * @returns {number}
     * @example
     * let a = Maths.zeroEqually(100, 0, 5); // -200
     * let a = Maths.zeroEqually(100, 1, 5); // -100
     * let a = Maths.zeroEqually(100, 2, 5); // 0
     * let a = Maths.zeroEqually(100, 3, 5); // 100
     * let a = Maths.zeroEqually(100, 4, 5); // 200
     */
    static zeroEqually(space: number, index: number, length: number): number {
        return (index - (length - 1) / 2) * space;
    }

    /**
     * 获取等分 min - max 后的平均点 ，不包括min,max
     * @param {number} [count=1] 等分几个
     * @param {number} min 
     * @param {number} max
     * @returns {number[]}
     * @example
     * let a = Maths.equally(1, 0, 10); //[5]
     * let b = Maths.equally(2, -10, 10); //[-5, 5]
     */
    static equally(count: number = 1, min: number, max: number): number[] {
        count++;
        let equally: number[] = [];
        let space = ((max - min) / count);
        for (let i = 1; i < count; i++) {
            equally.push(~~(min + space * i));
        }
        return equally;
    }

    /**
     * 判断一个向量是在一条线的上面还是下面
     * 0 在线上， 大于0在下，小于0在上
     * @param p 要判断的点
     * @param p1 点1
     * @param p2 点2
     * @returns bool
     */
    static outOrInTriangle(p: cc.Vec2, p1: cc.Vec2, p2: cc.Vec2 = cc.v2(0, 0)) {
        let a = p2.y - p1.y;
        let b = p1.x - p2.x;
        let c = p2.x * p1.y - p1.x * p2.y;
        let result = a * p.x + b * p.y + c;
        return result;
    }

    /**
     * 
     * @param end 
     * @param start 
     * @returns 
     */
    static getAngle(end: cc.Vec2, start = cc.v2(0, 0)) {
        //计算出朝向
        let dx = end.x - start.x;
        let dy = end.y - start.y;
        let dir = cc.v2(dx, dy);
        //根据朝向计算出夹角弧度
        let angle = dir.signAngle(cc.v2(1, 0));
        //将弧度转换为欧拉角
        let degree = angle / Math.PI * 180;
        return -degree
    }

    /**
     * 线段与线段是否相交
     * @param a1 第一条线段起点
     * @param a2 第一条线段终点
     * @param b1 
     * @param b2 
     */
    static lineLine(a1: cc.Vec3, a2: cc.Vec3, b1: cc.Vec3, b2: cc.Vec3): boolean {
        if (!a1 || !a2 || !b1 || !b2) return false;

        let ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x);
        let ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x);
        let u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);

        if (u_b !== 0) {
            let ua = ua_t / u_b;
            let ub = ub_t / u_b;
            if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1)
                return true;
            return false;
        };

        return false;
    };


    /**
     * 线段与矩形是否相交
     * @param a1 第一条线段起点
     * @param a2 第一条线段终点
     * @param b 矩形块
     */
    static lineRect(a1: cc.Vec3, a2: cc.Vec3, b: cc.Rect): boolean {
        if (!a1 || !a2 || !b) return false;

        let r0 = new cc.Vec3(b.x, b.y);
        let r1 = new cc.Vec3(b.x, b.yMax);
        let r2 = new cc.Vec3(b.xMax, b.yMax);
        let r3 = new cc.Vec3(b.xMax, b.y);

        if (this.lineLine(a1, a2, r0, r1))
            return true;
        if (this.lineLine(a1, a2, r1, r2))
            return true;
        if (this.lineLine(a1, a2, r2, r3))
            return true;
        if (this.lineLine(a1, a2, r3, r0))
            return true;

        return false;
    };

    /**
     * 线段与多边形是否相交
     * @param a1 
     * @param a2 
     * @param b 
     */
    static linePolygon(a1: cc.Vec3, a2: cc.Vec3, b: cc.Vec3[]) {
        if (!a1 || !a2 || !b)
            return false;

        for (let i = 0, length = b.length; i < length; ++i) {
            let b1 = b[i];
            let b2 = b[(i + 1) % length];

            if (this.lineLine(a1, a2, b1, b2))
                return true;
        };

        return false;
    };

    /**
    * 矩形与矩形是否相交
    * @param a 
    * @param b 
    */
    static rectRect(a: cc.Rect, b: cc.Rect) {
        if (!a || !b)
            return false;

        let a_min_x = a.x;
        let a_min_y = a.y;
        let a_max_x = a.x + a.width;
        let a_max_y = a.y + a.height;

        let b_min_x = b.x;
        let b_min_y = b.y;
        let b_max_x = b.x + b.width;
        let b_max_y = b.y + b.height;

        return a_min_x <= b_max_x &&
            a_max_x >= b_min_x &&
            a_min_y <= b_max_y &&
            a_max_y >= b_min_y
    };


    /**
     * 矩形是否与线段相交
     * @param a 
     * @param b 
     */
    static rectPolygon(a: cc.Rect, b: cc.Vec3[]) {
        if (!a || !b)
            return false;
        let i: number, l: number;
        let r0 = new cc.Vec3(a.x, a.y);
        let r1 = new cc.Vec3(a.x, a.yMax);
        let r2 = new cc.Vec3(a.xMax, a.yMax);
        let r3 = new cc.Vec3(a.xMax, a.y);

        if (this.linePolygon(r0, r1, b))
            return true;
        if (this.linePolygon(r1, r2, b))
            return true;
        if (this.linePolygon(r2, r3, b))
            return true;
        if (this.linePolygon(r3, r0, b))
            return true;
        // check if a contains b
        for (i = 0, l = b.length; i < l; ++i) {
            if (this.pointInPolygon(b[i], a.converPolygon()))
                return true;
        }
        // check if b contains a
        if (this.pointInPolygon(r0, b))
            return true;
        if (this.pointInPolygon(r1, b))
            return true;
        if (this.pointInPolygon(r2, b))
            return true;
        if (this.pointInPolygon(r3, b))
            return true;
        return false;
    };

    /**
     * 多边形与多边形相交
     * @param a 
     * @param b 
     */
    static polygonPolygon(a: cc.Vec3[], b: cc.Vec3[]): boolean {
        if (!a || !b)
            return false;
        let i: number, l: number;

        // check if a intersects b
        for (i = 0, l = a.length; i < l; ++i) {
            let a1 = a[i];
            let a2 = a[(i + 1) % l];

            if (this.linePolygon(a1, a2, b))
                return true;
        }

        // check if a contains b
        for (i = 0, l = b.length; i < l; ++i) {
            if (this.pointInPolygon(b[i], a))
                return true;
        }

        // check if b contains a
        for (i = 0, l = a.length; i < l; ++i) {
            if (this.pointInPolygon(a[i], b))
                return true;
        }

        return false;
    };

    /**
     * 点是否在圆形中
     * @param point 
     * @param value 
     */
    static pointInCircle(point: cc.Vec3, value: Circles): boolean {
        if (!point || !value)
            return false;
        let distance = point.sub(cc.v3(value.x, value.y)).mag();
        return distance < value.radius;
    };

    /**
     * 圆形与圆形是否相交
     * @param a 
     * @param b 
     */
    static circleCircle(a: Circles, b: Circles) {
        if (!a || !b)
            return false;
        let distance = (cc.v3(a.x, a.y) as cc.Vec3).sub(cc.v3(b.x, b.y) as cc.Vec3).mag();
        return distance < (a.radius + b.radius);
    };

    /**
    * 多边形与圆形是否相交
    * @param polygon 
    * @param circle 
    */
    static polygonCircle(polygon: cc.Vec3[], circle: Circles) {
        if (!polygon || !circle)
            return false;

        let position = cc.v3(circle.x, circle.y);
        if (this.pointInPolygon(position, polygon)) {
            return true;
        }
        for (let i = 0, l = polygon.length; i < l; i++) {
            let start = i === 0 ? polygon[polygon.length - 1] : polygon[i - 1];
            let end = polygon[i];
            if (this.pointLineDistance(position, start, end, true) < circle.radius) {
                return true;
            }
        }
        return false;
    };

    /**
     * 点是否在多边形中
     * @param point 
     * @param polygon 
     */
    static pointInPolygon(point: cc.Vec3 | cc.Vec2 | cc.Vec3, polygon: (cc.Vec4 | cc.Vec3)[]): boolean {
        if (!point || !polygon) return false;
        let inside = false;
        let x = point.x;
        let y = point.y;
        let length = polygon.length;
        for (let i = 0, j = length - 1; i < length; j = i++) {
            let xi = polygon[i].x, yi = polygon[i].y,
                xj = polygon[j].x, yj = polygon[j].y,
                intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
            if (intersect) inside = !inside;
        }
        return inside;
    }

    /**
     * 计算点到直线的距离。如果这是一条线段并且垂足不在线段内，则会计算点到线段端点的距离
     * @param point 点
     * @param start 线段起点
     * @param end 线段终点
     * @param isSegment 是否为线段
     */
    static pointLineDistance(point: cc.Vec3, start: cc.Vec3, end: cc.Vec3, isSegment: boolean) {
        if (!point || !start || !isSegment || !end)
            return false;

        let dx = end.x - start.x;
        let dy = end.y - start.y;
        let d = dx * dx + dy * dy;
        let t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / d;
        let p: cc.Vec3;
        if (!isSegment) {
            p = cc.v3(start.x + t * dx, start.y + t * dy);
        } else {
            if (d) {
                if (t < 0) p = start;
                else if (t > 1) p = end;
                else p = cc.v3(start.x + t * dx, start.y + t * dy);
            } else {
                p = start;
            }
        }

        dx = point.x - p.x;
        dy = point.y - p.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
    
    static getRectRotatePoints(rect: cc.Rect, angle: number, pt: cc.Vec2) {
        let array = [
            cc.v2(rect.x, rect.y),
            cc.v2(rect.x + rect.width, rect.y),
            cc.v2(rect.x + rect.width, rect.y + rect.height),
            cc.v2(rect.x, rect.y + rect.height),
        ];
        const RADIAN = 2 * Math.PI / 360;
        return array.map(v2 => {
            let out = cc.v2();
            let radian = -angle * RADIAN;
            out.x = (v2.x - pt.x) * Math.cos(radian) - (v2.y - pt.y) * Math.sin(radian) + pt.x;
            out.y = (v2.x - pt.x) * Math.sin(radian) + (v2.y - pt.y) * Math.cos(radian) + pt.y;
            return out;
        });
    }
}
